/**
 * @file
 * @author  michal.nezerka@gmail.com
 * @brief   Map Window Implementation
 */

#include <wx/dcclient.h>
#include <wx/dc.h>
#include <wx/pen.h>
#include <wx/stdpaths.h>
#include <vector>

#include "app.h"
#include "gpx.h"
#include "mapwnd.h"

using namespace wildwood;

enum
{
    idEventTileProvider = wxID_HIGHEST + 1,   // this one gets sent from TileProviderWorkerThread
};

BEGIN_EVENT_TABLE(MapWnd, wxWindow)
    EVT_ERASE_BACKGROUND(MapWnd::onErase)
    EVT_PAINT(MapWnd::onPaint)
    EVT_MOUSEWHEEL(MapWnd::OnMouseWheel)
    EVT_LEFT_DOWN(MapWnd::OnMouseLeftDown)
    EVT_LEFT_UP(MapWnd::OnMouseLeftUp)
    EVT_MOTION(MapWnd::OnMouseMotion)
    EVT_THREAD(idEventTileProvider, MapWnd::OnTileProviderEvent)
END_EVENT_TABLE()

MapWnd::MapWnd(wxWindow *parent) :
    wxWindow(parent, wxID_ANY),
    mCenterGeo(0, 0),
    mTileProvider(NULL),
    mDragging(false)
{
    mTileProvider = new TileProvider(wxT("OSM"), wxT("http://a.tile.openstreetmap.org/ZOOM/X/Y.png"), this, idEventTileProvider);
    setGeoPos(wxGetApp().mSettings->mLastPos);
    mCenter.mZoom = wxGetApp().mSettings->mDefaultZoom;
    wxGetApp().info(wxT("Starting at position (%f, %f)\n"), mCenterGeo.getLat(), mCenterGeo.getLon());
}

MapWnd::~MapWnd()
{
    wxGetApp().mSettings->mDefaultZoom = mCenter.mZoom;
    wxGetApp().mSettings->mLastPos = mCenterGeo;

    delete mTileProvider;
}

void MapWnd::onErase(wxEraseEvent& event)
{
    wxClientDC* clientDC = NULL;
    if (!event.GetDC())
        clientDC = new wxClientDC(this);

    wxDC* dc = clientDC ? clientDC : event.GetDC() ;

    wxSize sz = GetClientSize();
    dc->SetBackgroundMode(wxSOLID);
    dc->DrawRectangle(wxPoint(0, 0), sz);
    //dc->SetBackgroundMode(wxTRANSPARENT);

    if (clientDC)
        delete clientDC;
}

/*
Called by the system of by wxWidgets when the window needs
to be redrawn. You can also trigger this call by
calling Refresh()/Update(). */
void MapWnd::onPaint(wxPaintEvent& event)
{
    // depending on your system you may need to look at double-buffered dcs
    wxPaintDC dc(this);
    render(dc);
}

void MapWnd::render(wxDC& dc)
{
    wxSize sz = GetClientSize();
    //std::cout << "client size: " << sz.GetWidth() << " " << sz.GetHeight() << std::endl;
    wxPoint cntr(sz.GetWidth() / 2, sz.GetHeight() / 2);

    // DRAW TILES
    wxPen pen2(*wxBLUE, 1);
    dc.SetPen(pen2);

    int tlLeft = mCenter.mX - ceil((cntr.x - mCenter.mInX) / float(GeoUtils::TILE_SIZE));
    int tlTop = mCenter.mY - ceil((cntr.y - mCenter.mInY) / float(GeoUtils::TILE_SIZE));
    int tlRight = mCenter.mX + ceil((sz.GetWidth() - (cntr.x - mCenter.mInX)) / float(GeoUtils::TILE_SIZE)) - 1;
    int tlBottom = mCenter.mY + ceil((sz.GetHeight() - (cntr.y - mCenter.mInY)) / float(GeoUtils::TILE_SIZE)) - 1;
    wxGetApp().info(wxT("render tiles: (%d, %d) - (%d, %d) diff (%d, %d)\n"), tlLeft, tlTop, tlRight, tlBottom, tlRight - tlLeft, tlBottom - tlTop);


    for (int tileX = tlLeft; tileX <= tlRight; tileX++)
        for (int tileY = tlTop; tileY <= tlBottom; tileY++)
        {
            TilePos p(mCenter);
            p.mX = tileX;
            p.mY = tileY;
            int dcX = cntr.x - p.mInX - (mCenter.mX - p.mX) * GeoUtils::TILE_SIZE;
            int dcY = cntr.y - p.mInY - (mCenter.mY - p.mY) * GeoUtils::TILE_SIZE;
            wxBitmap* tileBitmap = mTileProvider->getTileImage(p);
            if (tileBitmap)
            {
                dc.DrawBitmap(*tileBitmap, dcX, dcY, true);
            } else {
                dc.DrawRectangle(dcX, dcY, GeoUtils::TILE_SIZE, GeoUtils::TILE_SIZE);
            }
            dc.DrawText(wxString::Format(wxT("(%d %d)"), p.mX, p.mY), dcX, dcY);
        }

    // CENTER CROSS
    wxPen pen(*wxRED, CROSS_WIDTH); // red pen of width CROSS_WIDTH
    dc.SetPen(pen);
    dc.DrawLine(cntr.x - CROSS_SIZE, cntr.y, cntr.x + CROSS_SIZE, cntr.y);
    dc.DrawLine(cntr.x, cntr.y - CROSS_SIZE, cntr.x, cntr.y + CROSS_SIZE);

    // INFO LINE
    dc.DrawText(wxString::Format(wxT("zoom: %d lat: %f lon: %f"), GetZoom(), mCenterGeo.getLat(), mCenterGeo.getLon()), 0, 0);

    dc.SetPen(wxNullPen);
}

void MapWnd::OnMouseWheel(wxMouseEvent& event)
{
    //std::cout << "Mouse wheel event: rotation: " << event.GetWheelRotation() << " delta: " << event.GetWheelDelta() << std::endl;

    int zoomIncrement = event.GetWheelRotation() / 120;

    SetZoom(GetZoom() + zoomIncrement);
};

void MapWnd::OnMouseLeftDown(wxMouseEvent& event)
{
    std::cout << "On left down" << std::endl;
    mDragOrigin = event.GetPosition();
    mDragging = true;
    event.Skip();
}

void MapWnd::OnMouseLeftUp(wxMouseEvent& event)
{
    std::cout << "On left up" << std::endl;
    mDragging = false;
    event.Skip();
}

void MapWnd::OnMouseMotion(wxMouseEvent& event)
{
    if (mDragging && event.Dragging())
    {
        wxCoord shiftX = event.GetX() - mDragOrigin.x;
        wxCoord shiftY = event.GetY() - mDragOrigin.y;
        MoveCenter(-1 * shiftX, -1 * shiftY);
        mDragOrigin = event.GetPosition();
    }
}

void MapWnd::SetZoom(const int newZoom)
{
    if (newZoom >= 0 && newZoom <= 19)
    {
        mCenter = GeoUtils::gpsPos2TilePos(mCenterGeo, newZoom);
    }
    Refresh();
    Update();
}

void MapWnd::setGeoPos(const GeoPos newGeoPos)
{
    mCenterGeo = newGeoPos;
    mCenter = GeoUtils::gpsPos2TilePos(mCenterGeo, mCenter.mZoom);
}
void MapWnd::MoveCenter(const int deltaX, const int deltaY)
{
    TilePos newPos = mCenter;
    newPos.move(deltaX, deltaY);
    //std::cout << "Shift from " << mCenter << " to " << newPos << std::endl;

    if (newPos.isValid())
    {
        mCenter = newPos;
        mCenterGeo = GeoUtils::tilePos2GeoPos(mCenter);
        // set new center
        //mCenter = GeoUtils::tilePos2GeoPos(newPos);
        Refresh();
        Update();
    }
    //else //std::cout << "!!!New pos is invalid" << std::endl;
}

void MapWnd::OnTileProviderEvent(wxThreadEvent& event)
{
    wxLogMessage("OnTileProviderEvent");
    Refresh();
    Update();
}
